package simpleTaskDispatcher.model.taskDispatchers.impl;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

import simpleTaskDispatcher.model.DispatchingChange;
import simpleTaskDispatcher.model.TaskQueue;
import simpleTaskDispatcher.model.enums.DispatchingEvent;
import simpleTaskDispatcher.model.taskDispatchers.AbstractTaskQueueDispatcher;
import simpleTaskDispatcher.model.taskDispatchers.ITaskQueueDispatcher;

public class Dispatchers {

	/**
	 * 
	 * This dispatcher gives all workers to the first task. If workers cannot be
	 * assigned to this task, it goes to the second oldest one, etc.
	 * 
	 */
	public static ITaskQueueDispatcher focusOnFirstsTaskQueue(
			int maximumNumberOfActiveTaskQueues,
			Comparator<TaskQueue> comparator) {
		return new AbstractTaskQueueDispatcher(maximumNumberOfActiveTaskQueues,
				comparator) {

			@Override
			protected void dispatchSorted(List<DispatchingChange> changes,
					DispatchingEvent event, List<TaskQueue> taskQueues,
					int freeWorkers, int plannedFreeWorkers,
					Object eventParameter) {

				for (int i = 0; i < taskQueues.size(); i++) {
					TaskQueue taskQueue = taskQueues.get(i);
					if (plannedFreeWorkers == 0) {
						break;
					}

					int plannedNbWorkers = taskQueue.getPlannedNbWorkers();

					int minWorkersToAdd = taskQueue.getMinimalWorkers()
							- plannedNbWorkers;
					int maxWorkersToAdd = taskQueue.getMaximalWorkers()
							- plannedNbWorkers;
					if (plannedFreeWorkers < minWorkersToAdd
							&& minWorkersToAdd > 0) {
						break;
					}

					int assignedWorkers = Math.min(maxWorkersToAdd,
							plannedFreeWorkers);
					if (assignedWorkers > 0) {
						changes.add(DispatchingChange.assign(taskQueue,
								assignedWorkers));
						plannedFreeWorkers -= assignedWorkers;
					}
				}

			}
		};
	}

	private static int sum(List<Integer> p) {
		int sum = 0;
		for (int i = 0; i < p.size(); i++) {
			sum += p.get(i);
		}
		return sum;
	}

	private static int indexOfSmallest(List<Integer> p) {
		int smallestIndex = 0;
		for (int i = 0; i < p.size(); i++) {
			if (p.get(i) < p.get(smallestIndex)) {
				smallestIndex = i;
			}
		}
		return smallestIndex;
	}

	// private static int indexOfSmallest(int[] p) {
	// int smallestIndex = 0;
	// for (int i = 0; i < p.length; i++) {
	// if (p[i] < p[smallestIndex]) {
	// smallestIndex = i;
	// }
	// }
	// return smallestIndex;
	// }

	private static int reAssign(int[] distanceFromMin, int[] distanceFromMax,
			int freeWorkers) {
		// set distanceFromMin to 0
		for (int i = 0; i < distanceFromMin.length; i++) {
			if (distanceFromMin[i] > 0) {
				int removed = distanceFromMin[i];
				distanceFromMin[i] -= removed;
				distanceFromMax[i] += removed;
				freeWorkers += removed;
			} else if (distanceFromMin[i] < 0) {
				int added = -1 * distanceFromMin[i];
				distanceFromMin[i] += added;
				distanceFromMax[i] -= added;
				freeWorkers -= added;
			}
		}

		if (freeWorkers < 0) {
			throw new RuntimeException(
					"Should not happen, but if it does, it's bad!");
		}

		while (freeWorkers > 0) {
			int smallestIndex = -1;
			for (int i = 0; i < distanceFromMin.length; i++) {
				if (distanceFromMax[i] != 0) {
					if (smallestIndex == -1
							|| distanceFromMin[i] < distanceFromMin[smallestIndex]) {
						smallestIndex = i;
					}
				}
			}
			if (smallestIndex == -1) {
				// could not place any more workers
				break;
			} else {
				distanceFromMin[smallestIndex]++;
				distanceFromMax[smallestIndex]--;
				freeWorkers--;
			}
		}
		return freeWorkers;

	}

	/**
	 * This dispatcher ensure every queues has its minimum worker. Then, it
	 * assigns workers equally among those queues.
	 * 
	 * Warning, if a queue needs 5 workers and an other needs 1 worker, (6-2,
	 * 7-3, 8-4, ...) are considered equality.
	 * 
	 * @param maximumNumberOfActiveTaskQueues
	 * @return
	 */
	public static ITaskQueueDispatcher giveEqualResource(
			int maximumNumberOfActiveTaskQueues,
			Comparator<TaskQueue> comparator) {
		return new AbstractTaskQueueDispatcher(maximumNumberOfActiveTaskQueues,
				comparator) {

			@Override
			public void dispatchSorted(List<DispatchingChange> changes,
					DispatchingEvent event, List<TaskQueue> taskQueues,
					int freeWorkers, int plannedFreeWorkers,
					Object eventParameter) {

				List<TaskQueue> queues = new ArrayList<TaskQueue>();
				List<Integer> nbWorkers = new ArrayList<Integer>();
				List<Integer> removables = new ArrayList<Integer>();
				for (int i = 0; i < taskQueues.size(); i++) {
					queues.add(taskQueues.get(i));
					nbWorkers.add(queues.get(i).getPlannedNbWorkers());
					removables.add(nbWorkers.get(i)
							- queues.get(i).getMinimalWorkers());
				}

				while (sum(removables) + plannedFreeWorkers < 0) {
					// Remove smallest
					int indexSmallest = indexOfSmallest(removables);

					if (queues.get(indexSmallest).getPlannedNbWorkers() > 0) {
						// Maybe some workers have been released and there is
						// not enough for all beginned task queues
						return;
					}

					queues.remove(indexSmallest);
					nbWorkers.remove(indexSmallest);
					removables.remove(indexSmallest);

				}

				int[] distanceFromMin = new int[queues.size()];
				int[] distanceFromMax = new int[queues.size()];
				for (int i = 0; i < queues.size(); i++) {
					TaskQueue queue = queues.get(i);
					distanceFromMin[i] = queue.getPlannedNbWorkers()
							- queue.getMinimalWorkers();
					distanceFromMax[i] = queue.getMaximalWorkers()
							- queue.getPlannedNbWorkers();
				}

				plannedFreeWorkers = reAssign(distanceFromMin, distanceFromMax,
						plannedFreeWorkers);

				for (int i = 0; i < queues.size(); i++) {
					TaskQueue queue = queues.get(i);
					int currentDistanceFromMin = queue.getPlannedNbWorkers()
							- queue.getMinimalWorkers();
					if (currentDistanceFromMin > distanceFromMin[i]) {
						changes.add(DispatchingChange.release(queue,
								currentDistanceFromMin - distanceFromMin[i]));
					} else if (currentDistanceFromMin < distanceFromMin[i]) {
						changes.add(DispatchingChange.assign(queue,
								distanceFromMin[i] - currentDistanceFromMin));
					}

				}

			}
		};
	}
	
}
